package supernova

import (
	"fmt"
	"sort"
)

const (
	AddDatabaseOperation    OperationType = "addDatabase"
	RemoveDatabaseOperation OperationType = "removeDatabase"
	AddTableOperation       OperationType = "addTable"
	RemoveTableOperation    OperationType = "removeTable"
	AddColumnOperation      OperationType = "addColumn"
	RemoveColumnOperation   OperationType = "removeColumn"
	ChangeColumnOperation   OperationType = "changeColumn"
	AddIndexOperation       OperationType = "addIndex"
	RemoveIndexOperation    OperationType = "removeIndex"
	AddForeignOperation     OperationType = "addForeign"
	RemoveForeignOperation  OperationType = "removeForeign"
	AddRecordsOperation     OperationType = "addRecords"
	ClearRecordsOperation   OperationType = "clearRecords"
	AllRecordsOperation     OperationType = "allRecords"
	ReplaceRecordsOperation OperationType = "replaceRecords"
)

func AddDatabase(name string) Operation {
	return Operation{
		Type:   AddDatabaseOperation,
		Params: []interface{}{name},
	}
}

func RemoveDatabase(name string) Operation {
	return Operation{
		Type:   RemoveDatabaseOperation,
		Params: []interface{}{name},
	}
}

func AddTable(table *Table) Operation {
	return Operation{
		Type:   AddTableOperation,
		Params: []interface{}{table},
	}
}

func RemoveTable(table *Table) Operation {
	return Operation{
		Type:   RemoveTableOperation,
		Params: []interface{}{table},
	}
}

func AddIndex(table *Table, index *Index) Operation {
	return Operation{
		Type:   AddIndexOperation,
		Params: []interface{}{table, index},
	}
}

func AddIndexs(table *Table, indexs []*Index) []Operation {
	list := make([]Operation, len(indexs))
	for i, index := range indexs {
		list[i] = AddIndex(table, index)
	}
	return list
}

func RemoveIndex(table *Table, index *Index) Operation {
	return Operation{
		Type:   RemoveIndexOperation,
		Params: []interface{}{table, index},
	}
}

func RemoveIndexs(table *Table, indexs []*Index) []Operation {
	list := make([]Operation, len(indexs))
	for i, index := range indexs {
		list[i] = RemoveIndex(table, index)
	}
	return list
}

func AddColumn(table *Table, column *Column) Operation {
	return Operation{
		Type:   AddColumnOperation,
		Params: []interface{}{table, column},
	}
}

func AddColumns(table *Table, columns []*Column) []Operation {
	list := make([]Operation, len(columns))
	for i, column := range columns {
		list[i] = AddColumn(table, column)
	}
	return list
}

func RemoveColumn(table *Table, column *Column) Operation {
	return Operation{
		Type:   RemoveColumnOperation,
		Params: []interface{}{table, column},
	}
}

func RemoveColumns(table *Table, columns []*Column) []Operation {
	list := make([]Operation, len(columns))
	for i, column := range columns {
		list[i] = RemoveColumn(table, column)
	}
	return list
}

func ChangeColumn(table *Table, column *Column) Operation {
	return Operation{
		Type:   ChangeColumnOperation,
		Params: []interface{}{table, column},
	}
}

func ChangeColumns(table *Table, columns []*Column) []Operation {
	list := make([]Operation, len(columns))
	for i, column := range columns {
		list[i] = ChangeColumn(table, column)
	}
	return list
}

func AddForeign(table *Table, foreign *Foreign) Operation {
	return Operation{
		Type:   AddForeignOperation,
		Params: []interface{}{table, foreign},
	}
}

func AddForeigns(table *Table, foreigns []*Foreign) []Operation {
	list := make([]Operation, len(foreigns))
	for i, foreign := range foreigns {
		list[i] = AddForeign(table, foreign)
	}
	return list
}

func RemoveForeign(table *Table, foreign *Foreign) Operation {
	return Operation{
		Type:   RemoveForeignOperation,
		Params: []interface{}{table, foreign},
	}
}

func RemoveForeigns(table *Table, foreigns []*Foreign) []Operation {
	list := make([]Operation, len(foreigns))
	for i, foreign := range foreigns {
		list[i] = RemoveForeign(table, foreign)
	}
	return list
}

func AddRecords(table *Table, records [][]interface{}) Operation {
	return Operation{
		Type:   AddRecordsOperation,
		Params: []interface{}{table, records},
	}
}

func ReplaceRecords(table *Table, records [][]interface{}) Operation {
	return Operation{
		Type:   ReplaceRecordsOperation,
		Params: []interface{}{table, records},
	}
}

func ClearRecords(table *Table) Operation {
	return Operation{
		Type:   ClearRecordsOperation,
		Params: []interface{}{table},
	}
}
func AllRecords(table *Table) Operation {
	return Operation{
		Type:   AllRecordsOperation,
		Params: []interface{}{table},
	}
}

func NewDiff(source *Table, desc *Table) *Diff {
	return &Diff{
		Source: source,
		Dest:   desc,
	}
}

type Diff struct {
	Source *Table
	Dest   *Table

	isInit bool
}

func (diff *Diff) init() {
	if diff.isInit {
		return
	}
	if diff.Source != nil {
		diff.Source.analysis()
	}
	if diff.Dest != nil {
		diff.Dest.analysis()
	}
	diff.isInit = true
}

func (diff *Diff) NewTable() *Table {
	if diff.Source == nil && diff.Dest != nil {
		return diff.Dest
	}
	return nil
}

func (diff *Diff) OldTable() *Table {
	if diff.Dest == nil && diff.Source != nil {
		return diff.Source
	}
	return nil
}

func (diff *Diff) Column() (oldList []*Column, newList []*Column, changeList []*Column) {
	nn := []int{}
	for name, index := range diff.Dest.columnMap {
		oldIndex, ok := diff.Source.columnMap[name]
		if !ok {
			nn = append(nn, index)
			continue
		}
		oldColumn := diff.Source.Columns[oldIndex]
		newColumn := diff.Dest.Columns[index]
		if oldColumn.IsDiff(newColumn) {
			changeList = append(changeList, newColumn)
		}
	}
	for name, index := range diff.Source.columnMap {
		if _, ok := diff.Dest.columnMap[name]; !ok {
			oldList = append(oldList, diff.Source.Columns[index])
			continue
		}
	}
	sort.Ints(nn)
	for _, index := range nn {
		newList = append(newList, diff.Dest.Columns[index])
	}
	return
}

func (diff *Diff) Index() (oldList []*Index, newList []*Index) {
	for name, index := range diff.Dest.indexMap {
		old, ok := diff.Source.indexMap[name]
		if !ok {
			newList = append(newList, diff.Dest.Indexs[index])
			continue
		}
		oldIndex := diff.Source.Indexs[old]
		newIndex := diff.Dest.Indexs[index]
		if oldIndex.IsDiff(newIndex) {
			oldList = append(oldList, oldIndex)
			newList = append(newList, newIndex)
		}
	}
	for name, index := range diff.Source.indexMap {
		if _, ok := diff.Source.indexMap[name]; !ok {
			oldList = append(oldList, diff.Source.Indexs[index])
			continue
		}
	}
	return
}

func (diff *Diff) Foreign() (oldList []*Foreign, newList []*Foreign) {
	for name, index := range diff.Dest.foreignMap {
		oldIndex, ok := diff.Source.foreignMap[name]
		if !ok {
			newList = append(newList, diff.Dest.Foreigns[index])
			continue
		}
		oldForeign := diff.Source.Foreigns[oldIndex]
		newForeign := diff.Dest.Foreigns[index]
		if oldForeign.IsDiff(newForeign) {
			oldList = append(oldList, oldForeign)
			newList = append(newList, newForeign)
		}
	}
	for name, index := range diff.Source.foreignMap {
		if _, ok := diff.Source.foreignMap[name]; !ok {
			oldList = append(oldList, diff.Source.Foreigns[index])
			continue
		}
	}
	return
}

type DiffSlice []Diff

func NewDiffSlice(source []*Table, desc []*Table) DiffSlice {
	sourceMap := map[string]*Table{}
	for index := range source {
		sourceMap[source[index].Name] = source[index]
	}
	descMap := map[string]*Table{}
	for index := range desc {
		descMap[desc[index].Name] = desc[index]
	}
	diffs := DiffSlice{}
	for name, table := range sourceMap {
		desc := descMap[name]
		diff := Diff{
			Source: table,
			Dest:   desc,
		}
		diff.init()
		diffs = append(diffs, diff)
	}
	for name, table := range descMap {
		_, ok := sourceMap[name]
		if ok {
			continue
		}
		diff := Diff{
			Source: nil,
			Dest:   table,
		}
		diff.init()
		diffs = append(diffs, diff)
	}
	return diffs
}

func (diffs DiffSlice) Operation() []Operation {
	operations := []Operation{}
	for _, diff := range diffs {
		newtable := diff.NewTable()
		if newtable != nil {
			operations = append(operations, AddTable(newtable))
			continue
		}
		oldtable := diff.OldTable()
		if oldtable != nil {
			operations = append(operations, RemoveTable(oldtable))
			continue
		}
		//table := diff.Dest
		oldColumns, newColumns, changeColumns := diff.Column()
		fmt.Println("column", diff.Dest.Name, oldColumns, newColumns, changeColumns)
		oldForeign, newForeign := diff.Foreign()
		oldIndex, newIndex := diff.Index()
		if oldColumns != nil {
			operations = append(operations, RemoveColumns(diff.Source, oldColumns)...)
		}
		if oldForeign != nil {
			operations = append(operations, RemoveForeigns(diff.Source, oldForeign)...)
		}
		if oldIndex != nil {
			operations = append(operations, RemoveIndexs(diff.Source, oldIndex)...)
		}
		if changeColumns != nil {
			operations = append(operations, ChangeColumns(diff.Dest, changeColumns)...)
		}
		if newColumns != nil {
			operations = append(operations, AddColumns(diff.Dest, newColumns)...)
		}
		if newForeign != nil {
			operations = append(operations, AddForeigns(diff.Dest, newForeign)...)
		}
		if newIndex != nil {
			operations = append(operations, AddIndexs(diff.Dest, newIndex)...)
		}
	}
	return operations
}
